import karamba
import re
from commands import getoutput


###########################################################################
# This is a collection of functions trying to work around the problems with dcop usage
# in SuperKaramba. For the most part, just giving DCOP commands works,
# from both: command line and importing kde/qt libs methodn.
# what does NOT WORK : asking superkaramba itself through dcop for answers.
# Using normal methods, comand like 
# "dcop sk-1234 qt find SomeWidget" will lock up SK theme solid.
# 
# There is only one, true and tested approach to asking something from SuperKaramba
# through DCOP - pushing the requests out of karamba.executeInteractive
# and catching and assembling the responses by hand.
# 
# This module tries to make it easier to ask particularly common questions
# from SuperKaramba over DCOP:
# - What other superkaramba themes are running in ALL of superkaramba threads?
# - Is there a copy on this same theme running somewhere in other SuperKaramba thread?
# - How can I close the right theme, or control how many copies of this
#   theme are running at the same time?
# - Is there life on Mars? errr... wait a minute...

print "dcophelper imported"

class executedThread:
	'''
	This class name is a bit counter-intuitive.
	It is not the thread itself, but the setup for processing
	of what was returnd by a thread.
	As you can see, we don't run the actual thread here.
	An instance of this class only behaves as an instruction
	on what to do when the labeled thread returns.
	You can assign these classes as labels to a
	dict type of running-threads database inventory.
	'''
	def __init__(self, arguments=None):
		'''
		In here the most important part is the set up of the function
		that will be run when out thread is signalled as "returned"
		arguments - a dict type with only 2 keys:
		 'fn' - will hold a function object
		 'args' - will hold a dict of arguments specific for that funciton.
		  please, make sure that function is ready to process arguments passed as one dict object.
		'''
		###print "executedThread: initing args ", arguments
		self.function = None # will be assigned a funciton object. Make sure NOT to pass () as part of function name.
		self.args = {} # will be assigned a dict type with vars to be passed to function when run.
		if arguments:
			self.function = arguments['fn']
			if arguments.has_key('args'):
				self.args.update(arguments['args'])
		
	def returned(self, arguments=None):
		'''
		This is the most important part of this class.
		Whatever processes the data, after it returns, it calls this
		method of that particular instance of this class.
		Note the self.function assignment in the __init__ section.
		'''
		###print "executedThread: returning args ", arguments
		if arguments:
			self.args.update(arguments)
		if self.function:
			self.function(self.args)

class dcophelper:
	def __init__(self, widget, function2call):
		###print "widget is ", widget, hex(widget)
		
		# public, can rely on.
		self.ready = False
		self.homethread = ''
		self.themename_unique = '' # this will be filled in automatically based on marker.
		self.themename = '' # the long name is "unique" as it contains " - \number" at the end.
		self.themelist = {}
		self.function2call = function2call # the most important part. That is how we know the vales are ready.
		self.competitor = {} # will be used as signal that other threads of same theme are running.

		# private, may change
		self.__skrawdata__ = {} # will be populated by collectOutput
		self.__threads__ = {} # these are querry thread tags for catching executeInteractive output
		self.__sknames__ = getoutput('dcop | grep superkaramba').splitlines()
		self.__markerObj__ = karamba.createMenu(widget)
		schstring = '(?<=qt/karamba - )[^/]+(?=/unnamed\d+\(KPopupMenu, '+hex(self.__markerObj__)+')'
		
		# print 'dcophelper: __sknames__: ', self.__sknames__

		# testin if all lines of output start with "superkaramba"
		line = self.__sknames__.__len__() - 1
		trash = []
		while line > -1:
			if self.__sknames__[line][:12] != "superkaramba":
				trash.append(self.__sknames__.pop(line))
			line -= 1
		
		if trash:
			print "Output polluted by the following message:\n", '\n'.join(trash)
			self.__sknames__ = []
			args = {
				'widget':widget,
				'message':'Output polluted. Stopping.',
				'message_long':'Your system is broken.\nCommand line output is polluted by the following message:\n'+'\n'.join(trash)+'\nFix your system, complain to its packager, and try again.',
				}
			self.__reportBack__(args, flag='warning')

		# adding dcop requests as separate threads to the SuperKaramba's executeInteractive
		# each time one thread returns, __collectOutput__ is called
		# note each thread may produce multiple bursts of output for the same request.
		for skname in self.__sknames__:
			#print 'dcophelper: starting queries, skname is ', skname
			mycommand = ["dcop", skname, "qt", "find", "KPopupMenu"]
			self.__threads__[karamba.executeInteractive(widget, mycommand)] = executedThread({'fn':self.__collectOutput__,'args':{'widget':widget,'skname':skname}})

		# while threads are working, we will do extra computations.
		self.__nameExtractor__ = re.compile('(?<=qt/karamba - )[^/]+(?=/unnamed)')
		self.__markerExtractor__ = re.compile(schstring)

	def __collectOutput__(self, args):

		# print '\n__collectOutput__:\n', args, '\n'

		# just storing the current output chunk in history.
		if self.__skrawdata__.has_key(args['skname']):
			# sometimes the output is split and comes in in portions
			# so, we need to reassemble the parts
			self.__skrawdata__[args['skname']]= self.__skrawdata__[args['skname']]+'\n'+args['output']
			# print "collectOutput: detected multi-chunk output for ", args['skname']
		else:
			self.__skrawdata__[args['skname']] = args['output']
			# print "collectOutput: detected first chunk output for ", args['skname']
		
		# Parsing ONLY CURRENT output for theme names (and end of output marker)
		# and adding them to the self.themelist
		# it is a dict type with "long" theme names as keys, 
		# and superkaramba thread names as values.
		# example: {'A-Foto -1':'superkaramba-12354','A-Foto - 2':'superkaramba-43323'}
		# this will be exposed to the user.
		last = ''
		for line in args['output'].splitlines():
			# print "collectOutput: name-scanning :", line
			if line.find('qt/ThemesLayout/unnamed') > -1:
				# end of output for this thread
				try:
					self.__sknames__.remove(args['skname'])
				except:
					pass
				# we will test for the lendth of __sknames__ later and quit if 0
				# print "collectOutput: detected the end of thread's output"
				break
			else:
				try:
					name = self.__nameExtractor__.search(line).group()
				except:
					name = None
				# print "collectOutput: name is :", name
				if name and name != last:
					# print "collectOutput: new theme name. Documenting."
					self.themelist.update({name:args['skname']})
					last = name
		#print "collectOutput: themelist is ", self.themelist, type(self.themelist)

		# this is ran on every Output chunk, until we find out "this is us" marker.
		# print "collectOutput: themename exists ", bool(self.themename_unique)
		if not self.themename_unique:
			if self.__markerExtractor__.search(args['output']):
				self.themename_unique = self.__markerExtractor__.search(args['output']).group()
				self.themename = self.themename_unique.split()[0]
				self.homethread = args['skname']
				# print "collectOutput: found our OWN name: ", self.themename_unique

		# assuming "this is us" marker is set, this is ran after every output chunk came in.
		# self.themelist is update with new names every time so we just check there.
		# our goal is to detect a presence of another theme with the same "short" name
		# we are looking for name, with no " - \[number]" in the end.
		# DETECTING IF WE ARE NOT ALONE
		if (not self.competitor) and self.themename_unique and self.themelist.__len__() > 1:
			if ','.join(self.themelist.keys()).count(self.themename_unique.split()[0]) > 1:
				self.competitor['skThread'] = None
				# we need to short-assign it here because of threading
				# if we don't do it in time, another thread will start the same.
				# at least that will evaluate to True
				# print "collectOutput: found a competing afoto theme"
				self.competitor['skThread'] = self.findTwin()
				args['message'] = 'Another A-Foto detected.'
				args['message_long'] = 'A-Foto is already running\nUse main SuperKaramba window to start one more A-Foto.'
				self.__reportBack__(args, 'warning')

		# DETECTING IF THIS IT THE END OF OUTPUT
		if self.__sknames__.__len__() < 1:
			self.__reportBack__(args, 'done')

	def __reportBack__(self, args, flag='done'):
		try:
			competeThread = self.competitor['skThread']
		except:
			competeThread = None
		values = {
			'themenameunique':self.themename_unique,
			'themename':self.themename,
			'themelist':self.themelist,
			'homethread':self.homethread,
			'competeThread':competeThread,
			'flag':flag,
			}
		args.update(values)
		if flag == 'done':
			karamba.deleteMenu(args['widget'], self.__markerObj__)
			self.__markerObj__ = None # this will be our "already reported" marker.
		self.function2call(args['widget'], args)

	def findTwin(self):
		# note, we are INTOLERANT of theme names with spaces. Spaces are name-cutting markers.
		# if spaces are used in theme name, we are screwed across the board.
		values = None
		for theme in self.themelist.keys():
			if theme.split()[0] == self.themename:
				# we found our guy. Now we make sure it's not us. :)
				if theme != self.themename_unique:
					values = self.themelist[theme]
		return values

	def __exec__(self, mycommand):
		'''
		Takes a string and passes it as a command
		'''
		#karamba.execute('dcop '+sk+' KarambaIface closeTheme "'+appName+'"')
		#karamba.execute('dcop '+sk+' KarambaIface quit')
		karamba.execute(mycommand)
	
	def closeTheme(self, themename=None):
		'''
		Really tries to guess what you are trying to say.
		Returns bool for success / fail.
		If themename is not passed, killing ourselves.
		'''
		if not themename:
			if self.themename:
				# killing ourselves
				mycommand = 'dcop '+self.homethread+' KarambaIface closeTheme '+self.themename
				self.__exec__(mycommand)
		else:
			keys = self.themelist.keys()
			#keys.__delitem__(keys.index(self.themename_unique))
			if ','.join(keys).count(themename) == 1:
				mycommand = 'dcop '+self.themelist[themename]+' KarambaIface closeTheme '+themename
				self.__exec__(mycommand)
#
###########################################################################